
#include <Liberty3D/Engine/XML/Parser.h>
#include <Liberty3D/Engine/Windows/Stream.h>

#include <vcclr.h>
#include <vector>

using namespace Liberty::Engine::Xml;
using namespace System;
using namespace System::Text;
using namespace System::IO;
using namespace System::Xml;
using namespace cli;

#pragma unmanaged

std::shared_ptr<INode> Parser::parse(std::ios& i) {
    return parse(i.rdbuf());
}

#pragma managed

namespace Liberty { namespace Engine { namespace Xml {
    class Node : public INode {
    protected:
        gcroot<XmlNode^> _node;

    public:
        Node(XmlNode^ node);

    public:
        virtual Type        type() const;
        virtual std::string data() const;

        virtual bool        hasAttribute(const std::string&) const;
        virtual std::string attribute(const std::string& name, const std::string& defaultValue) const;
        virtual void        setAttribute(const std::string& name, const std::string& v);

        virtual void                   childs(std::vector< std::shared_ptr<INode> >&) const;
        virtual std::shared_ptr<INode> firstChild() const;
        virtual std::shared_ptr<INode> firstChildElement(const std::string&) const;

        virtual std::shared_ptr<INode> nextSiblingElement(const std::string&) const;

        virtual std::shared_ptr<INode> addElement(const std::string& name, const std::shared_ptr<INode>& before);
        virtual std::shared_ptr<INode> addText   (const std::string& data, const std::shared_ptr<INode>& before);

        virtual void removeChild(const std::shared_ptr<INode>& child);

        virtual std::string toString() const;
        virtual void toXml(const std::string& fileName) const;
        virtual void toXml(std::ios&) const;
        virtual void toXml(std::streambuf* buf) const;
    };
}}}

Node::Node(XmlNode^ node) : _node(node) {
}

Node::Type Node::type() const {
    switch (_node->NodeType) {
    case XmlNodeType::Document: return DOC;
    case XmlNodeType::Element:  return ELEMENT;
    case XmlNodeType::Text:     return TEXT;
    case XmlNodeType::CDATA:    return CDATA;
    default:                    return UNKNOWN;
    }
}

static std::string Native_To_UTF8(System::String^ n) {
    if (n == nullptr) {
        return std::string();
    }

    Encoding^              u8          = Encoding::UTF8;
    array<unsigned char>^  bytes       = u8->GetBytes(n);
    pin_ptr<unsigned char> bytesPinned = &bytes[0];

    return std::string((char*)(unsigned char*)bytesPinned, bytes->Length);
}

static String^ UTF8_To_Native(const std::string& item) {
    return gcnew String(item.c_str(), 0, (int)item.size(), Encoding::UTF8);
}

std::string Node::data() const {
    switch (_node->NodeType) {
    case XmlNodeType::Element:  return Native_To_UTF8(_node->Name);
    default:                    return Native_To_UTF8(_node->Value);
    }

    return std::string();
}

bool Node::hasAttribute(const std::string& item) const {
    return _node->Attributes->GetNamedItem(UTF8_To_Native(item)) != nullptr;
}

std::string Node::attribute(const std::string& item, const std::string& defaultValue) const {
    XmlNode^ val = _node->Attributes->GetNamedItem(UTF8_To_Native(item));

    if (val == nullptr) {
        return defaultValue;
    }

    return Native_To_UTF8(val->Value);
}

void Node::setAttribute(const std::string& name, const std::string& v) {
    XmlAttribute^ val;

    if (v.length() == 0) {
        val = (XmlAttribute^)_node->Attributes->GetNamedItem(UTF8_To_Native(name));

        if (val != nullptr) {
            _node->Attributes->Remove(val);
            return;
        }
    }
    else {
        val = _node->OwnerDocument->CreateAttribute(UTF8_To_Native(name));
        val->Value = UTF8_To_Native(v);
        _node->Attributes->SetNamedItem(val);
    }
}

void Node::childs(std::vector< std::shared_ptr<INode> >& ch) const {
    XmlNodeList^ list = _node->ChildNodes;
    int          index, count = list->Count;

    ch.clear();

    for (index = 0; index < count; index++) {
        ch.push_back(std::shared_ptr<INode>(new Node(list[index])));
    }
}

std::shared_ptr<INode> Node::firstChild() const {
    XmlNodeList^ list = _node->ChildNodes;

    if (list->Count > 0) {
        return std::shared_ptr<INode>(new Node(list[0]));
    }

    return std::shared_ptr<INode>();
}

std::shared_ptr<INode> Node::firstChildElement(const std::string& ch) const {
    String^      name = ch.length()? UTF8_To_Native(ch): nullptr;
    XmlNodeList^ list = _node->ChildNodes;
    int          index, count = list->Count;

    for (index = 0; index < count; index++) {
        XmlNode^ node = list[index];

        if (node->NodeType == XmlNodeType::Element) {
            if ((name == nullptr) || node->Name->Equals(name)) {
                return std::shared_ptr<INode>(new Node(node));
            }
        }
    }

    return std::shared_ptr<INode>();
}

std::shared_ptr<INode> Node::nextSiblingElement(const std::string& ch) const {
    String^  name = ch.length()? UTF8_To_Native(ch): nullptr;
    XmlNode^ next = _node->NextSibling;

    while (next != nullptr) {
        if (next->NodeType == XmlNodeType::Element) {
            if ((name == nullptr) || next->Name->Equals(name)) {
                return std::shared_ptr<INode>(new Node(next));
            }
        }

        next = next->NextSibling;
    }

    return std::shared_ptr<INode>();
}

std::shared_ptr<INode> Node::addElement(const std::string& name, const std::shared_ptr<INode>& before) {
    XmlElement^ e = _node->OwnerDocument->CreateElement(UTF8_To_Native(name));

    if (before) {
        _node->InsertBefore(e, ((Node*)before.get())->_node);
    }
    else {
        _node->AppendChild(e);
    }

    return std::shared_ptr<INode>(new Node(e));
}

std::shared_ptr<INode> Node::addText(const std::string& data, const std::shared_ptr<INode>& before) {
    XmlNode^ nn = nullptr;

    if (containsWhitespaces(data)) {
        nn = _node->OwnerDocument->CreateCDataSection(UTF8_To_Native(data));
    }
    else {
        nn = _node->OwnerDocument->CreateTextNode(UTF8_To_Native(data));
    }

    if (before) {
        _node->InsertBefore(nn, ((Node*)before.get())->_node);
    }
    else {
        _node->AppendChild(nn);
    }

    return std::shared_ptr<INode>(new Node(nn));
}

void Node::removeChild(const std::shared_ptr<INode>& child) {
    if (child) {
        _node->RemoveChild(((Node*)child.get())->_node);
    }
}

std::string Node::toString() const {
    return Native_To_UTF8(_node->OuterXml);
}

void Node::toXml(const std::string& fileName) const {
    XmlWriter^ xmlWriter = XmlWriter::Create(UTF8_To_Native(fileName));

    _node->WriteTo(xmlWriter);
    xmlWriter->Close();
}

void Node::toXml(std::ios& s) const {
    toXml(s.rdbuf());
}

void Node::toXml(std::streambuf* buf) const {
    XmlWriter^ xmlWriter = XmlWriter::Create(CreateOutputStream(buf));

    _node->WriteTo(xmlWriter);
    xmlWriter->Close();
}

std::shared_ptr<INode> Parser::parse(std::streambuf* buf) {
    XmlDocument^ doc = gcnew XmlDocument();

    doc->Load(CreateInputStream(buf));
    return std::shared_ptr<INode>(new Node(doc));
}

std::shared_ptr<INode> Parser::parse(const std::string& buf) {
    XmlDocument^ doc = gcnew XmlDocument();

    try {
        doc->Load(UTF8_To_Native(buf));
        return std::shared_ptr<INode>(new Node(doc));
    }
    catch (Exception^) {
        return std::shared_ptr<INode>();
    }
}

std::shared_ptr<INode> Parser::parseString(const std::string& buf) {
    XmlDocument^ doc = gcnew XmlDocument();

    doc->LoadXml(UTF8_To_Native(buf));
    return std::shared_ptr<INode>(new Node(doc));
}
